﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using GL = OpenTK.Graphics.OpenGL.GL;
using ClearBufferMask = OpenTK.Graphics.OpenGL.ClearBufferMask;
using MatrixMode = OpenTK.Graphics.OpenGL.MatrixMode;
using PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat;
using PixelType = OpenTK.Graphics.OpenGL.PixelType;
using SimpleModeler.Graphics;
using SimpleModeler.GUI;
using SimpleModeler.Properties;
using System.Drawing;
using System.IO;

namespace SimpleModeler
{
    /// <summary>
    /// Das Hauptfenster für den SimpleModeler
    /// </summary>
    public class ModelerWindow : GameWindow
    {
        #region Fields
        private double _angleX = 45;
        private double _angleY = -45;
        private float _moveX = -15.0f;
        private float _moveY = -15.0f;
        private float _moveZ = 15.0f;
        private int _keyRotationSpeed = 2;
        private bool _changeCameraView = false;

        private bool _changePosX = false;
        private bool _changePosY = false;
        private bool _changePosZ = false;

        private MouseButton _cameraChangingButton = MouseButton.Right;
        private MouseButton _selectObjectButton = MouseButton.Left;

        private float tmpMoveCos;
        private float tmpMoveSin;
        #endregion // Fields


        #region Properties
        /// <summary>
        /// Liefert die Modeler-Instanz für das Modeler-Window
        /// </summary>
        public Modeler Modeler { get; private set; }

        /// <summary>
        /// Betrachtungswinkel bezogen auf die X-Achse
        /// </summary>
        public double AngleX
        {
            get { return _angleX; }
            set { _angleX = value > 360 ? value - 360 : value < -360 ? value + 360 : value; }
        }

        /// <summary>
        /// Betrachtungswinkel bezogen auf die Y-Achse
        /// </summary>
        public double AngleY
        {
            get { return _angleY; }
            set { _angleY = value > 360 ? value - 360 : value < -360 ? value + 360 : value; }
        }
        #endregion // Properties


        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        public ModelerWindow(int width, int height, GraphicsMode mode, string title) : base(width,height, mode, title)
        {
            this.VSync = VSyncMode.On;
            this.Closing += new EventHandler<System.ComponentModel.CancelEventArgs>(ModelerWindow_Closing);
        }
        #endregion // Constructors


        #region Methods
        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            this.Modeler = new Modeler(this, Settings.Default.UseLighting);
            this.Mouse.ButtonDown += new EventHandler<MouseButtonEventArgs>(Mouse_ButtonDown);
            this.Mouse.ButtonUp += new EventHandler<MouseButtonEventArgs>(Mouse_ButtonUp);
            this.Mouse.Move += new EventHandler<MouseMoveEventArgs>(Mouse_Move);
            this.Mouse.WheelChanged += new EventHandler<MouseWheelEventArgs>(Mouse_WheelChanged);
        }

        /// <summary>
        /// Called when your window is resized. Set your viewport here. It is also
        /// a good place to set up your projection matrix (which probably changes
        /// along when the aspect ratio of your window).
        /// </summary>
        /// <param name="e">Not used.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();

            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Helper.DegreeToRadian(45.0), 
                (float)ClientRectangle.Width / (float)ClientRectangle.Height, 0.1f, 300.0f);
            GL.LoadMatrix(ref projection);
            GL.MatrixMode(MatrixMode.Modelview);
        }

        /// <summary>
        /// Called when it is time to setup the next frame. Add your logic here.
        /// </summary>
        /// <param name="e">Contains timing information for framerate independent logic.</param>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);

            if (Keyboard[Key.Escape]) this.Close(); // schließen wenn ESC
            if (Keyboard[Key.T]) this.Modeler.ViewToolbar(); // Toolbar öffnen wenn T

            if (Keyboard[Key.Up]) _moveY -= 0.1f;  // Ansicht nach oben bewegen
            if (Keyboard[Key.Down]) _moveY += 0.1f; // Ansicht nach unten bewegen

            if (Keyboard[Key.Left]) AngleY -= _keyRotationSpeed; // Ansicht nach links schwenken
            if (Keyboard[Key.Right]) AngleY += _keyRotationSpeed; // Ansicht nach rechts schwenken

            tmpMoveCos = (float)Math.Round((-Math.Cos(Helper.DegreeToRadian(AngleY)) / 10), 10);
            tmpMoveSin = (float)Math.Round((-Math.Sin(Helper.DegreeToRadian(AngleY)) / 10), 10);
            
            if (Keyboard[Key.W])
            {// Nach vorne bewegen
                _moveZ += tmpMoveCos;
                _moveX += tmpMoveSin;
            }
            if (Keyboard[Key.S])
            {// Nach hinten bewegen
                _moveZ -= tmpMoveCos;
                _moveX -= tmpMoveSin;
            }
            if (Keyboard[Key.A])
            {// Nach links bewegen
                _moveZ += tmpMoveSin;
                _moveX -= tmpMoveCos;
            }
            if (Keyboard[Key.D])
            {// Nach rechts bewegen
                _moveZ -= tmpMoveSin;
                _moveX += tmpMoveCos;
            }

            if (this.Modeler != null)
            {// Wenn der Modeler instanziert wurde...
                if (Keyboard[Key.Delete] && Modeler.SelectedObject != null) // Entfernt das selektierte Objekt
                    this.Modeler.Delete(Modeler.SelectedObject.ID);
                else
                {
                    SceneObject selectedObject = Modeler.SelectedObject;
                    if (selectedObject != null)
                    {
                        //Moving the selected Object
                        if (Keyboard[Key.O])
                        {   //rauf
                            if (Keyboard[Key.AltLeft] || Keyboard[Key.AltRight])
                                selectedObject.Rotation += new Vector3(0.0f, -1.0f, 0.0f);
                            else
                                selectedObject.Coordinate += new Vector3(0.0f, -0.1f, 0.0f);
                        }
                        if (Keyboard[Key.U])
                        {   //runter
                            if (Keyboard[Key.AltLeft] || Keyboard[Key.AltRight])
                                selectedObject.Rotation += new Vector3(0.0f, 1.0f, 0.0f);
                            else
                                selectedObject.Coordinate += new Vector3(0.0f, 0.1f, 0.0f);
                        }

                        if (Keyboard[Key.I])
                        {//tiefer
                            if (Keyboard[Key.ShiftLeft] || Keyboard[Key.ShiftRight])
                                selectedObject.Coordinate += new Vector3(0.0f, 0.0f, -0.1f);
                            else if (Keyboard[Key.AltLeft] || Keyboard[Key.AltRight])
                                selectedObject.Rotation += new Vector3(0.0f, 0.0f, -1.0f);
                            else
                                selectedObject.Coordinate += new Vector3(-tmpMoveSin, 0.0f, tmpMoveCos);
                        }
                        if (Keyboard[Key.K])
                        {//zurueck
                            if (Keyboard[Key.ShiftLeft] || Keyboard[Key.ShiftRight])
                                selectedObject.Coordinate += new Vector3(0.0f, 0.0f, 0.1f);
                            else if (Keyboard[Key.AltLeft] || Keyboard[Key.AltRight])
                                selectedObject.Rotation += new Vector3(0.0f, 0.0f, 1.0f);
                            else
                                selectedObject.Coordinate += new Vector3(tmpMoveSin, 0.0f, -tmpMoveCos);
                        }
                        if (Keyboard[Key.J])
                        {//links
                            if (Keyboard[Key.ShiftLeft] || Keyboard[Key.ShiftRight])
                                selectedObject.Coordinate += new Vector3(-0.1f, 0.0f, 0.0f);
                            else if (Keyboard[Key.AltLeft] || Keyboard[Key.AltRight])
                                selectedObject.Rotation += new Vector3(-1.0f, 0.0f, 0.0f);
                            else
                                selectedObject.Coordinate += new Vector3(tmpMoveCos, 0.0f, tmpMoveSin);
                        }
                        if (Keyboard[Key.L])
                        {//rechts
                            if (Keyboard[Key.ShiftLeft] || Keyboard[Key.ShiftRight])
                                selectedObject.Coordinate += new Vector3(0.1f, 0.0f, 0.0f);
                            else if (Keyboard[Key.AltLeft] || Keyboard[Key.AltRight])
                                selectedObject.Rotation += new Vector3(1.0f, 0.0f, 0.0f);
                            else
                                selectedObject.Coordinate += new Vector3(-tmpMoveCos, 0.0f, -tmpMoveSin);
                        }
                    }
                }
                Modeler.UpdateFrame(e);
            }
        }

        /// <summary>
        /// Called when it is time to render the next frame. Add your rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
            GL.Clear(ClearBufferMask.ColorBufferBit);
            GL.Clear(ClearBufferMask.DepthBufferBit);
            GL.LoadIdentity();

            this.SetCamera(_angleX, _angleY);
            GL.Translate(_moveX, _moveY, -_moveZ);

            if (this.Modeler != null) Modeler.Render();
            this.SwapBuffers();
        }


        #region Mouse-EventHandling
        void Mouse_Move(object sender, MouseMoveEventArgs e)
        {
            if (_changeCameraView)
            {// Wenn mit der Mausbewegung die Kameraansicht bewegt werden soll
                AngleY += e.XDelta;
                if (AngleX + e.YDelta < 90 && AngleX + e.YDelta > -90)
                    AngleX += e.YDelta;
            }
            else if (this.Modeler != null && this.Modeler.SelectedObject != null)
            {// Andernfalls soll das selektierte Objekt bewegt werden
                float fak = 20.0f;

                if (_changePosX)
                {// Wenn mit der Maus die X-Achse markiert wurde...
                    float x = (tmpMoveCos >= 0 ? -1 : 1) * (e.XDelta / fak);
                    this.Modeler.SelectedObject.Coordinate += new Vector3(x, 0.0f, 0.0f);
                }
                else if (_changePosY)
                {// Wenn mit der Maus die Y-Achse markiert wurde...
                    this.Modeler.SelectedObject.Coordinate += new Vector3(0.0f, -e.YDelta/fak, 0.0f);
                }
                else if (_changePosZ)
                {// Wenn mit der Maus die Z-Achse markiert wurde...
                    float z = (tmpMoveSin >= 0 ? -1 : 1) * (e.XDelta / fak);                    
                    this.Modeler.SelectedObject.Coordinate += new Vector3(0.0f, 0.0f, z);
                }
            }
        }

        void Mouse_ButtonUp(object sender, MouseButtonEventArgs e)
        {// Setzen der diversen temporären Parameter auf false, wenn eine Maustaste losgelassen wird
            if (e.Button == _cameraChangingButton)
                _changeCameraView = false;
            else
            {
                _changePosX = false;
                _changePosY = false;
                _changePosZ = false;
            }
        }

        void Mouse_ButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.Button == _cameraChangingButton)
                _changeCameraView = true;
            else if (e.Button == _selectObjectButton && this.Modeler != null)
            {// Wenn versucht wird zu selektieren, müssen die Objekte in der ID-Farbe gerendert werden                
                this.Modeler.Render(true);
                
                Byte4 pixel = new Byte4();
                GL.ReadPixels(e.X, this.Height-e.Y, 1, 1, PixelFormat.Rgba, PixelType.UnsignedByte, ref pixel);
                ID clickedID = ID.GetIDFromColor(pixel.A, pixel.R, pixel.G, pixel.B);
                
                if (clickedID.Equals(Color.Red)) // Wurde die X-Achse ausgewählt?
                    _changePosX = true;
                else if (clickedID.Equals(Color.Green)) // Wurde die Y-Achse ausgewählt?
                    _changePosY = true;
                else if (clickedID.Equals(Color.Blue)) // Wurde die Z-Achse ausgewählt?
                    _changePosZ = true;
                else // Wurde ein Objekt ausgewählt?
                    this.Modeler.Select(clickedID);
            }
        }

        void Mouse_WheelChanged(object sender, MouseWheelEventArgs e)
        {
            if ((Keyboard[Key.ShiftLeft] || Keyboard[Key.ShiftRight]) && (this.Modeler != null && this.Modeler.SelectedObject != null))
            {// Wenn ein Objekt selektiert ist und Shift gedrückt wird, kann das Objekt vergrößert werden
                float scl = e.DeltaPrecise / 10.0f;
                this.Modeler.SelectedObject.Size += new Vector3(scl, scl, scl);
            }
            else
            {// Wenn Shift nicht gedrückt wird, wird die Ansicht mit dem Mausrad verkleinert und vergrößert
                _moveZ -= (tmpMoveCos >= 0 ? -1 : 1) * e.DeltaPrecise;
                _moveX -= (tmpMoveSin >= 0 ? -1 : 1) * e.DeltaPrecise;
                _moveY += e.DeltaPrecise;
            }
        }
        #endregion // Mouse-EventHandling

        void ModelerWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { this.Modeler.Close(); }

        /// <summary>
        /// Setzt die Kamera nach den übergebenen Einstellungen
        /// </summary>
        /// <param name="angleX">Betrachtungswinkel bezogen auf die X-Achse</param>
        /// <param name="angleY">Betrachtungswinkel bezogen auf die Y-Achse</param>
        private void SetCamera(double angleX, double angleY)
        {
            float eyeX = (float)-Math.Sin(Helper.DegreeToRadian(angleY));
            float eyeY = (float)Math.Sin(Helper.DegreeToRadian(angleX));
            float eyeZ = (float)Math.Cos(Helper.DegreeToRadian(angleY));
            Matrix4 lookat = Matrix4.LookAt(eyeX, eyeY, eyeZ, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref lookat);
        }
        #endregion // Methods


        /*********************************************************************/
        /*********************************MAIN********************************/
        /*********************************************************************/
        #region MAIN
        private static ModelerWindow _mw;
        /// <summary>
        /// Liefert die Instanz des SimpleModeler-Fensters zurück
        /// </summary>
        public static ModelerWindow Instance { get { return _mw; } }

        /// <summary>
        /// Einstiegspunkt des SimpleModelers
        /// </summary>
        [STAThread]
        public static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            string windowTitle = Settings.Default.Title;
            ColorFormat format = 32;
            int depth = Settings.Default.Depth;
            int stencil = 8;
            int samples = Settings.Default.Samples;
            int width = Settings.Default.Width;
            int height = Settings.Default.Height;
            string iconFile = Settings.Default.IconsPath;
            // The 'using' idiom guarantees proper resource cleanup.
            // We request 30 UpdateFrame events per second, and unlimited
            // RenderFrame events (as fast as the computer can handle).
            using (_mw = new ModelerWindow(width, height, new GraphicsMode(format, depth, stencil, samples), windowTitle))
            {
                if(File.Exists(iconFile))
                    _mw.Icon = new Icon(iconFile);
                _mw.Run(60, 60);
            }
        }
        #endregion // MAIN
    }
}
